/** 
 * @file Valores.java
 *
 * Implementación de la clase abstracta \c Valores.
 * 
 * @author Luis Ernesto Carrera Retana
 * @date 2009-08-18
 */

package estadistec.logica.tipos;

import estadistec.logica.parser.IEvaluable;

import estadistec.logica.Constantes;

import estadistec.excepciones.ExcepcionFormatoNumeros;
import estadistec.excepciones.ExcepcionMetodoInexistente;
import estadistec.excepciones.ExcepcionCapacidadIndiceFueraRango;
import estadistec.excepciones.ExcepcionCapacidadExcedida;
import estadistec.excepciones.ExcepcionCapacidadNegativa;


/** 
 * Clase abstracta que va a servir para guardar los valores para cada uno de 
 * los diferentes tipos de atributos. Debe tenerse cuidado que los valores
 * son 0-indexados, por lo que los índices aceptados van de 0 a 
 * <\c noCasos - 1>.
 * 
 * Además, en cada clase, falta definir el constructor.
 *
 **/
public abstract class Valores<T> implements IEvaluable
{

	/** 
	 * Versión de la clase para que sea serializable.
	 */
	static final long serialVersionUID = 1L;


	/** 
	 * Devuelve el tipo de los valores.
	 */
	abstract public TipoValor getTipo ();

	/** 
	 * Arreglo con los valores de tipo T.
	 */
	private T[] _valores;


	/** 
	 * Devuelve el número de decimales que se muestran. Aplica cuando se
	 * utiliza punto flotante en la representación del valor.
	 *
	 * @throws NoSuchMethodException El valor no utiliza punto flotante.
	 */
	abstract public int getNoDecimales () 
		throws ExcepcionMetodoInexistente;

	/** 
	 * Guarda de manera interna el número de decimales. Aplicable a aquellos
	 * atributos que utilizan punto flotante.
	 * 
	 * @param noDecimales Número de decimales
	 *
	 * @throws NoSuchMethodException El atributo no utiliza punto flotante.
	 * @throws IllegalArgumentException El número de decimales no es válido.
	 */
	abstract public void setNoDecimales (int noDecimales)
		throws ExcepcionMetodoInexistente;


	/** 
	 * Constructor.
	 * 
	 * @param noCasos Número de casos del atributo.
	 *
	 * @throws ExcepcionCapacidadNegativa El número de casos es negativo.
	 *
	 * @throws ExcepcionCapacidadExcedida El número de casos excede el 
	 * permitido. @see Constantes.MAX_NO_CASOS.
	 */
	public Valores (int noCasos) 
		throws ExcepcionCapacidadNegativa, ExcepcionCapacidadExcedida
	{
		if (noCasos < 0)
			throw new ExcepcionCapacidadNegativa("" + noCasos);
		if (noCasos > Constantes.MAX_NO_CASOS)
			throw new ExcepcionCapacidadExcedida("" + noCasos);
		this._noCasos = noCasos;
		this._valores = (T[]) new Object[noCasos + 20];
		assert (_valores != null);
	}


	/** 
	 * Número de elementos del arreglo @p _valores. El tamaño del arreglo 
	 * se sabe utilizando el valor de la variable \c valores.length.
	 */
	private int _noCasos;

	/** 
	 * Devuelve el número de casos.
	 */
	public int getNoCasos() {
		return this._noCasos;
	};


	/** 
	 * Método que convierte el texto, en el valor correspondiente.
	 * 
	 * @param texto String del valor.
	 *
	 * @throws ExcepcionFormatoNumeros El texto no corresponde a un
	 * valor del tipo dado.
	 */
	abstract protected T _obtenerValor(String texto) 
		throws ExcepcionFormatoNumeros;

	/** 
	 * Método que devuelve un string a partir del valor. La implementación
	 * depende del tipo del atributo.
	 */
	abstract protected String _obtenerTexto(T valor);

	/** 
	 * El valor por defecto para aquellos casos que no estén definidos,
	 * o de que se accese a un índice inexistente.
	 */
	private T _valorXDefecto;

	/** 
	 * Define un valor por defecto.
	 * 
	 * @param textoValor String con el valor por defecto.
	 * 
	 * @return El texto del valor por defecto.
	 *
	 * Para excepciones @see _obtenerValor
	 */
	public void setValorXDefecto (String textoValor)
	{
		this._valorXDefecto = this._obtenerValor(textoValor);
	}

	/** 
	 * Define el Blanco como valor por defecto.
	 */
	public void quitarValorXDefecto () {
		this._valorXDefecto = null;
	}

	/** 
	 * Devuelve el string que representa al valor por defecto.
	 */
	public String getTextoValorXDefecto() {
		return (this._obtenerTexto(this._valorXDefecto));
	}

	/** 
	 * Devuelve el valor por defecto o \c null si no está definido.
	 */
	public T getValorXDefecto() {
		return this._valorXDefecto;
	}


	/** 
	 * Devuelve el texto del valor almacenado en \c indice.
	 * 
	 * @param indice Índice en base 0.
	 * 
	 * @return Texto del valor almacenado.
	 *
	 * @throws ExcepcionCapacidadIndiceFueraRango El índice no señala a ningún caso
	 * correcto.
	 */
	public String getTexto(int indice) throws ExcepcionCapacidadIndiceFueraRango{
		if (indice < 0 || indice >= this._noCasos)
			throw new ExcepcionCapacidadIndiceFueraRango("Indíce: " + indice);
		return (this._obtenerTexto(this._valores[indice]));
	}

	/** 
	 * Devuelve el valor especificado. En caso de que el índice no 
	 * corresponda a ningún valor, o que la casilla esté en blanco devuelve
	 * el valor por defecto. Si devuelve un \c null es porque el valor por
	 * defecto es el blanco.
	 * 
	 * @param indice Índice del valor solicitado 0-indexado.
	 */
	public T getValor(int indice) {
		if (indice < 0 || indice >= this._noCasos 
				|| this._valores[indice] == null)
			return (this._valorXDefecto);
		else
			return (this._valores[indice]);
	}

	/** 
	 * Define o redefine un valor, a partir del valor dado.
	 * 
	 * @param indice Índice del valor que se quiere modificar.
	 * @param valor Nuevo valor.
	 * 
	 * @throws ExcepcionCapacidadIndiceFueraRango En caso de que el índice no 
	 * corresponda a un valor.
	 */
	protected void _setValorInterno(int indice, T valor) 
		throws ExcepcionCapacidadIndiceFueraRango{
		if (indice < 0 || indice >= this._noCasos)
			throw new ExcepcionCapacidadIndiceFueraRango("Índice: " + indice);
		this._valores[indice] = valor;
	}

	/** 
	 * Define o redefine un valor, a partir de un texto.
	 * 
	 * @param indice Índice del valor que se quiere definir o modificar.
	 * @param textoValor Texto del valor.
	 * 
	 * @return El texto del valor dado.
	 *
	 * @throws ExcepcionFormatoNumeros @see _obtenerValor.
	 *
	 * @throws ExcepcionCapacidadIndiceFueraRango El índice está fuera del rango.
	 */
	public String set(int indice, String textoValor) 
		throws ExcepcionCapacidadIndiceFueraRango, ExcepcionFormatoNumeros
	{
		if (indice < 0 || indice >= this._noCasos)
			throw new ExcepcionCapacidadIndiceFueraRango("Índice: " + indice);
		this._valores[indice] = this._obtenerValor(textoValor);
		return (this._obtenerTexto(this._valores[indice]));
	};


	/** 
	 * Borra un grupo de valores, dejándolos en blanco.
	 * 
	 * @param indice Índice del valor inicial a borrar.
	 * @param noCasos Número de casos a borrar
	 *
	 * @throws ExcepcionCapacidadIndiceFueraRango El índice esté fuera del rango.
	 */
	public void borrar(int indice, int noCasos) 
		throws ExcepcionCapacidadIndiceFueraRango 
	{
		if (indice < 0 || noCasos < 1 || indice + noCasos > this._noCasos)
			throw new ExcepcionCapacidadIndiceFueraRango("Índice : " + indice);
		for (int j = indice; j < indice + noCasos; j++)
			this._valores[j] = null;
	}


	/** 
	 * Elimina las casillas especificadas. Se reduce el número de casos.
	 * 	- Si el tamaño del arreglo es mayor que el doble del número de casos,
	 * 	se hace una copia que tenga la mitad del tamaño. Luego se copian los
	 * 	valores en dos partes:
	 * 		- Los valores que están "antes" de los valores eliminados
	 * 		- y los valores que están después.
	 *	- En caso de que no haya que crear una copia del arreglo, se va 
	 *	copiando elemento por elemento en los espacios disponibles.
	 * 
	 * @param indice Índice de la primera casilla que se quiere eliminar.
	 *
	 * @param noCasos Número de casillas a eliminar.
	 */
	public void eliminarCasos(int indice, int noCasos) 
		throws ExcepcionCapacidadIndiceFueraRango
	{
		System.out.println(">> Valores.eliminarCasos");
		assert (indice >= 0 && indice <= this._noCasos);
		assert (indice + noCasos - 1 <= this._noCasos);
		assert (noCasos > 0);
		if (2 * (this._noCasos - noCasos) < this._valores.length) {
			try {
				T[] copia = (T[]) new Object[this._valores.length/2 + 1];
				System.arraycopy(this._valores, 0, copia, 0, indice);
				System.arraycopy(this._valores, indice + noCasos, copia, indice,
						this._noCasos - noCasos - indice);
				this._valores = copia;
			} catch (Exception e) {
				e.printStackTrace();
			}
		} else {
			int j;
			int copiarHasta = this._noCasos - noCasos;
			/* moviendo los valores. */
			for (j = indice; j < copiarHasta; j++) {
				System.out.println("j = " + j);
				this._valores[j] = this._valores[j + noCasos];
			}
			/* borrando los valores al final. */
			for (j = copiarHasta; j < this._noCasos; j++)
				this._valores[j] = null;
		}
		this._noCasos -= noCasos;
		System.out.println("<< Valores.eliminarCasos");
	}

	/** 
	 * Agrega casos. El arreglo tiene dos tamaños: el número de casos
	 * en la variable \c _noCasos y la longitud del arreglo. 
	 * 	- Si la longitud es suficiente para el número de casos que se agregan 
	 * 	entonces no hace falta crear una copia. 
	 * 	- Si se agrega @em un caso y no es suficiente la longitud del arreglo, 
	 * 	se crea una copia del arreglo con el doble de tamaño.
	 * 	- Si se agrega más de un caso y el tamaño el arreglo no es suficiente, 
	 * 	se asume que el usuario sabe qué tamaño quiere, por lo que la longitud 
	 * 	de la copia es del tamaño exacto.
	 * 
	 * @param indice 0-indexado índice a dónde se agregan los casos.
	 * @param noCasos Número de casos a agregar.
	 *
	 * @throws ExcepcionCapacidadIndiceFueraRango
	 * 	- En caso de que el índice sea negativo
	 * 	- En caso de que el índice sea mayor al número de casos (un índice
	 * 	igual al número de casos quiere decir que se agregan al final)
	 *
	 * @throws ExcepcionCapacidadExcedida El número de casos que se agregan
	 * hace que se exceda la capacidad máxima permitida.
	 */
	public void insertarCasos (int indice, int noCasos) 
		throws ExcepcionCapacidadIndiceFueraRango, ExcepcionCapacidadExcedida
	{
		assert (this._valores != null);
		if (indice < 0 || indice > this._noCasos || noCasos < 1)
			throw new ExcepcionCapacidadIndiceFueraRango("Índice: " + indice);
		if (this._noCasos + noCasos > Constantes.MAX_NO_CASOS)
			throw new ExcepcionCapacidadExcedida("Índice: " + indice);
		if (this._valores.length < this._noCasos + noCasos) {
			int size;
			if (noCasos == 1)
				size = 2 * this._valores.length;
			else
				size = this._noCasos + noCasos;
			T[] copia = (T[]) new Object[size];
			System.arraycopy(this._valores, 0, copia, 0, indice);
			System.arraycopy(this._valores, indice, copia, indice + noCasos, 
					this._noCasos - indice);
			this._valores = copia;
		} else 
			for (int j = this._noCasos - 1; j >= indice; j--)
				this._valores[j + noCasos] = this._valores[j];
		this._noCasos += noCasos;
	}

	/** 
	 * Devuelve el texto de los valores del atributo.
	 */
	public String[] getTextoValores() 
	{
		String[] resp = new String[this._noCasos];

		for (int j = 0; j < this._noCasos; j++)
			resp[j] = this._obtenerTexto(this.getValor(j));

		return resp;
	}

	public void setValor(int indice, Object valor) {
		this._setValorInterno(indice, (T) valor);
	}

}
